/*
 * Distributed under the OpenDDS License.
 * See: http://www.opendds.org/license.html
 */

#include "DCPS/DdsDcps_pch.h" //Only the _pch include should start with DCPS/

#include "Timers.h"

#include "debug.h"

#include <ace/Reactor.h>

#if OPENDDS_CONFIG_BOOTTIME_TIMERS
#  if defined __linux__ && __linux__
#    include <sys/timerfd.h>
#  else
#    error Unsupported platform for OPENDDS_CONFIG_BOOTTIME_TIMERS
#  endif
#  include "RcHandle_T.h"
#endif

OPENDDS_BEGIN_VERSIONED_NAMESPACE_DECL

namespace OpenDDS {
namespace DCPS {
namespace Timers {

const TimerId InvalidTimerId = -1;

#if OPENDDS_CONFIG_BOOTTIME_TIMERS

// CLOCK_BOOTTIME timers on Linux can be set using the timerfd_* functions.
// The file descriptor created by timerfd_ can be used with ACE's reactor so
// that events generated by these timers can be demultiplexed along with other
// events on the same thread.
// To maintain the ACE timers abstraction for users of the OpenDDS::DCPS::Timers
// functions, we'll translate the timerfd's input/read event to handle_timeout.

struct TimerFdHandler : RcEventHandler {
  TimerFdHandler(RcEventHandler& userHandler, ACE_HANDLE fd, const void* context)
    : userHandler_(userHandler)
    , fd_(fd)
    , context_(context)
  {}

  int handle_input(ACE_HANDLE)
  {
    const MonotonicTimePoint now = MonotonicTimePoint::now();
    uint64_t unused;
    const ssize_t readReturned = read(fd_, &unused, sizeof unused);
    if (readReturned != sizeof unused) {
      if (log_level >= LogLevel::Warning) {
        ACE_ERROR((LM_WARNING, "(%P|%t) WARNING: TimerFdHandler::handle_input: read %m\n"));
      }
      return -1;
    }
    const RcHandle<RcEventHandler> user = userHandler_.lock();
    return user ? user->handle_timeout(now.value(), context_) : -1;
  }

  int handle_close(ACE_HANDLE, ACE_Reactor_Mask) { close(fd_); return 0; }

  ACE_HANDLE get_handle() const { return fd_; }

  const WeakRcHandle<RcEventHandler> userHandler_;
  const ACE_HANDLE fd_;
  const void* const context_;
};
#endif

TimerId schedule(ACE_Reactor* reactor,
                 RcEventHandler& handler,
                 const void* arg,
                 const TimeDuration& delay,
                 const TimeDuration& interval)
{
#if OPENDDS_CONFIG_BOOTTIME_TIMERS
  const ACE_HANDLE fd = timerfd_create(CLOCK_BOOTTIME, TFD_CLOEXEC);
  if (fd == -1) {
    if (log_level >= LogLevel::Notice) {
      ACE_ERROR((LM_NOTICE, "(%P|%t) NOTICE: Timers::schedule: timerfd_create %m\n"));
    }
    return InvalidTimerId;
  }
  const RcHandle<TimerFdHandler> fdHandler = make_rch<TimerFdHandler>(ref(handler), fd, arg);
  if (reactor->register_handler(fdHandler.get(), ACE_Event_Handler::READ_MASK) == -1) {
    if (log_level >= LogLevel::Notice) {
      ACE_ERROR((LM_NOTICE, "(%P|%t) NOTICE: Timers::schedule: register_handler %m\n"));
    }
    return InvalidTimerId;
  }
  static const timespec one_ns = {0, 1};
  itimerspec ts;
  ts.it_interval = interval.value();
  if (delay < TimeDuration::zero_value) {
    // expiration in the past, execute as soon as possible (see note about zeros below)
    ts.it_value = one_ns;
  } else if (delay == TimeDuration::zero_value) {
    // avoid zeros since that would disarm the timer
    // if the interval is positive, use that as the initial expiration
    ts.it_value = (interval == TimeDuration::zero_value) ? one_ns : interval.value();
  } else {
    ts.it_value = delay.value();
  }
  if (timerfd_settime(fd, 0, &ts, 0) == -1) {
    if (log_level >= LogLevel::Notice) {
      ACE_ERROR((LM_NOTICE, "(%P|%t) NOTICE: Timers::schedule: timerfd_settime %m\n"));
    }
    return InvalidTimerId;
  }
  return fd;
#else
  return reactor->schedule_timer(&handler, arg, delay.value(), interval.value());
#endif
}

void cancel(ACE_Reactor* reactor, TimerId timer)
{
#if OPENDDS_CONFIG_BOOTTIME_TIMERS
  reactor->remove_handler(static_cast<ACE_HANDLE>(timer), ACE_Event_Handler::ALL_EVENTS_MASK);
#else
  reactor->cancel_timer(timer);
#endif
}

}
}
}

OPENDDS_END_VERSIONED_NAMESPACE_DECL
